package cn.yx.common.core.util.qrcode;

import cn.hutool.core.util.StrUtil;
import cn.yx.common.core.constant.message.qrcode.QrCodeUtilMessageKey;
import cn.yx.common.core.enums.CharsetEnum;
import cn.yx.common.core.exception.CommonException;
import cn.yx.common.core.util.awt.AwtStringUtil;
import cn.yx.common.core.util.image.ImageUtil;
import cn.yx.common.core.util.io.FileUtil;
import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import lombok.extern.slf4j.Slf4j;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * <p>二维码工具类</p>
 *
 * @author Wgssmart
 */
@Slf4j
public final class QrCodeUtil {

    private static final Map<EncodeHintType, Object> ENCODE_HINTS_MAP = new HashMap<>(3);

    private static final Map<DecodeHintType, Object> DECODE_HINTS_MAP = new HashMap<>(1);

    private QrCodeUtil() {
    }

    static {
        ENCODE_HINTS_MAP.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        ENCODE_HINTS_MAP.put(EncodeHintType.CHARACTER_SET, CharsetEnum.UTF8.getValue());
        ENCODE_HINTS_MAP.put(EncodeHintType.MARGIN, 1);

        DECODE_HINTS_MAP.put(DecodeHintType.CHARACTER_SET, CharsetEnum.UTF8.getValue());
    }

    /**
     * 根据二维码生成选项生成二维码图片对象
     *
     * @param qrCodeOption
     * @return
     */
    public static BufferedImage generateQrCode(QrCodeOption qrCodeOption) {
        if (StrUtil.isEmpty(qrCodeOption.getText())) {
            // @SimplifiedChinese=二维码输出选项文本内容为空
            // @English=QRCode option text is empty
            throw new CommonException(QrCodeUtilMessageKey.QR_CODE_OPTION_TEXT_IS_EMPTY);
        }
        BufferedImage bufferedImage = new BufferedImage(qrCodeOption.getWidth(), qrCodeOption.getHeight(),
                BufferedImage.TYPE_INT_RGB);
        Graphics graphics = bufferedImage.getGraphics();
        // remark每一行的字符串
        List<String> remarkMultiRows = null;
        // remark的行高
        int remarkLineHeight = 0;
        if (StrUtil.isNotEmpty(qrCodeOption.getRemark())) {
            FontMetrics fontMetrics = graphics.getFontMetrics(qrCodeOption.getRemarkFont());
            remarkLineHeight = fontMetrics.getHeight();
            remarkMultiRows = AwtStringUtil.multiRows(qrCodeOption.getRemark(),
                    qrCodeOption.getWidth() - qrCodeOption.getRemarkPadding() * 2, fontMetrics);
        }
        // 包含边距的备注的高度
        int wholeRemarkHeight = 0;
        // 获取备注的总高度
        if (Objects.nonNull(remarkMultiRows)) {
            wholeRemarkHeight = remarkMultiRows.size() * remarkLineHeight + qrCodeOption.getRemarkPadding();
        }
        // 获取整张二维码图片的高度
        int wholeHeight = qrCodeOption.getHeight() + wholeRemarkHeight;
        // 重新生成缓冲图
        bufferedImage = new BufferedImage(qrCodeOption.getWidth(), wholeHeight, BufferedImage.TYPE_INT_RGB);
        drawQrCode(bufferedImage, qrCodeOption);
        drawRemark(bufferedImage, qrCodeOption, wholeRemarkHeight, remarkLineHeight, remarkMultiRows);
        drawLogo(bufferedImage, qrCodeOption);
        return bufferedImage;
    }

    /**
     * 绘制二维码背景和二维码
     *
     * @param bufferedImage
     * @param qrCodeOption  二维码生成选项
     */
    private static void drawQrCode(BufferedImage bufferedImage, QrCodeOption qrCodeOption) {
        try {
            BitMatrix bitMatrix = new MultiFormatWriter().encode(qrCodeOption.getText(), BarcodeFormat.QR_CODE,
                    qrCodeOption.getWidth(), qrCodeOption.getHeight(), ENCODE_HINTS_MAP);
            for (int x = 0; x < qrCodeOption.getWidth(); x++) {
                for (int y = 0; y < qrCodeOption.getHeight(); y++) {
                    bufferedImage.setRGB(x, y, bitMatrix.get(x, y) ? qrCodeOption.getRgbQrColor() : qrCodeOption.getRgbBackgroundColor());
                }
            }
        } catch (WriterException e) {
            log.error("qr code encode error: {}", e.getMessage());
            // @SimplifiedChinese=生成二维码异常
            // @English=Encode QRCode error
            throw new CommonException(QrCodeUtilMessageKey.DECODE_QR_CODE_ERROR, e);
        }
    }

    /**
     * 绘制备注信息
     *
     * @param bufferedImage
     * @param qrCodeOption      二维码生成选项
     * @param wholeRemarkHeight 备注区域总高度
     * @param remarkLineHeight  备注的行高
     * @param remarkMultiRows   备注的每行字符串
     */
    private static void drawRemark(BufferedImage bufferedImage, QrCodeOption qrCodeOption,
                                   Integer wholeRemarkHeight, Integer remarkLineHeight, List<String> remarkMultiRows) {
        if (Objects.nonNull(remarkMultiRows)) {
            Graphics graphics = bufferedImage.getGraphics();
            graphics.setColor(new Color(qrCodeOption.getRgbBackgroundColor()));
            graphics.fillRect(0, qrCodeOption.getHeight(), qrCodeOption.getWidth(), wholeRemarkHeight);
            graphics.setColor(qrCodeOption.getRemarkColor());
            graphics.setFont(qrCodeOption.getRemarkFont());
            for (int row = 0, size = remarkMultiRows.size(); row < size; row++) {
                String lineText = remarkMultiRows.get(row);
                int lineWidth = AwtStringUtil.getStringLineWidth(lineText, graphics.getFontMetrics(qrCodeOption.getRemarkFont()));
                int lineX = (qrCodeOption.getWidth() - lineWidth) / 2;
                int lineY = qrCodeOption.getHeight() + qrCodeOption.getRemarkPadding() + row * remarkLineHeight;
                graphics.drawString(lineText, lineX, lineY);
            }
        }
    }

    /**
     * 绘制二维码中央logo
     *
     * @param bufferedImage
     * @param qrCodeOption  二维码生成选项
     */
    private static void drawLogo(BufferedImage bufferedImage, QrCodeOption qrCodeOption) {
        BufferedImage logoImage = getLogoImage(qrCodeOption);
        if (Objects.nonNull(logoImage)) {
            int logoWidth = (int) (qrCodeOption.getWidth() * qrCodeOption.getLogoScale());
            int logoHeight = (int) (qrCodeOption.getWidth() * qrCodeOption.getLogoScale());
            int logoX = (qrCodeOption.getWidth() - logoWidth) / 2;
            int logoY = (qrCodeOption.getHeight() - logoHeight) / 2;
            Graphics graphics = bufferedImage.getGraphics();
            graphics.setColor(new Color(qrCodeOption.getRgbBackgroundColor()));
            graphics.fillRect(logoX - qrCodeOption.getLogoPadding(), logoY - qrCodeOption.getLogoPadding(),
                    logoWidth + qrCodeOption.getLogoPadding() * 2, logoHeight + qrCodeOption.getLogoPadding() * 2);
            graphics.drawImage(logoImage, logoX, logoY, logoWidth, logoHeight, null);
        }
    }

    /**
     * 获取logo的BufferedImage对象
     *
     * @param qrCodeOption 二维码生成选项
     * @return
     */
    private static BufferedImage getLogoImage(QrCodeOption qrCodeOption) {
        if (Objects.nonNull(qrCodeOption.getLogoUrl())) {
            return ImageUtil.bufferedImageFromUrl(qrCodeOption.getLogoUrl());
        }
        if (Objects.nonNull(qrCodeOption.getLogoFile())) {
            return ImageUtil.bufferedImage(qrCodeOption.getLogoFile());
        }
        return null;
    }

    /**
     * 通过二维码生成选项，生成二维码图片到指定文件
     *
     * @param qrCodeOption 二维码生成选项
     * @param filePath     二维码保存路径
     */
    public static void saveQrCode(QrCodeOption qrCodeOption, String filePath) {
        try {
            BufferedImage bufferedImage = generateQrCode(qrCodeOption);
            ImageIO.write(bufferedImage, FileUtil.getExt(filePath), new File(filePath));
        } catch (IOException e) {
            log.error("save qr code error: {}", e.getMessage());
            // @SimplifiedChinese=保存二维码异常
            // @English=Save QRCode error
            throw new CommonException(QrCodeUtilMessageKey.SAVE_QR_CODE_ERROR, e);
        }
    }

    /**
     * 解析二维码
     *
     * @param image 二维码BufferedImage
     * @return
     */
    public static String decodeQrCode(BufferedImage image) {
        LuminanceSource source = new BufferedImageLuminanceSource(image);
        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
        try {
            Result result = new MultiFormatReader().decode(bitmap, DECODE_HINTS_MAP);
            return Objects.nonNull(result) ? result.getText() : null;
        } catch (NotFoundException e) {
            log.error("decode qr code error: {}", e.getMessage());
            // @SimplifiedChinese=解析二维码异常
            // @English=Decode QRCode error
            throw new CommonException(QrCodeUtilMessageKey.DECODE_QR_CODE_ERROR, e);
        }
    }

    /**
     * 解析指定路径的二维码
     *
     * @param imgPath 二维码图片路径
     * @return
     */
    public static String decodeQrCode(String imgPath) {
        return decodeQrCode(ImageUtil.bufferedImage(imgPath));
    }

    /**
     * 解析指定url的二维码图片
     *
     * @param imgUrl 二维码图片url
     * @return
     */
    public static String decodeQrCodeFromUrl(String imgUrl) {
        return decodeQrCode(ImageUtil.bufferedImageFromUrl(imgUrl));
    }

}
